package eval

import (
	"showen/ast"
	"showen/lex"
	"showen/obj"
	"showen/parse"
	"testing"
)

func TestEvalExpression001(t *testing.T) {
	tests := []struct {
		input string
		want  string
	}{
		{"#t", "#t"},
		{"1", "1"},
		{"1/2", "1/2"},
		{"2/4", "1/2"},
		{"1.23", "1.23"},
		{"1/2+0.34i", "1/2+0.34i"},
		{"#\\A", "#\\A"},
		{`"abc"`, `"abc"`},
		{"(car '(a . b))", "a"},
		{"(cdr '(a . b))", "b"},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("parser.ParseExpression() error = %v", err)
		}

		var o obj.Object
		o, err = EvalExpression(expr, env)
		if err != nil {
			t.Fatalf("EvalExpression() error = %v", err)
		}

		if o.Inspect() != tt.want {
			t.Errorf("EvalExpression() = %v, want %v", o.Inspect(), tt.want)
		}
	}
}

func TestEvalProgram001(t *testing.T) {
	tests := []struct {
		input string
		want  []string
	}{
		{
			"#t #t",
			[]string{"#t", "#t"},
		},
		{
			`"hello" #t`,
			[]string{`"hello"`, "#t"},
		},
		{
			`(+ 1 1)`,
			[]string{"2"},
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		exprs, err := parser.ParseProgram()
		if err != nil {
			t.Fatalf("parser.ParseExpression() error = %v", err)
		}

		for i, expr := range exprs {
			var o obj.Object
			o, err = EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if o.Inspect() != tt.want[i] {
				t.Errorf("EvalExpression() = %v, want %v", o.Inspect(), tt.want[0])
			}
		}
	}
}

func TestEvalExpression002(t *testing.T) {
	tests := []struct {
		input string
		want  string
	}{
		{"'(a . b)", "(a . b)"},
		{"'()", "()"},
		{"'(a b)", "(a b)"},
		{"'(a b c)", "(a b c)"},
		{"'(a b c)", "(a b c)"},
		{"'(a . ())", "(a)"},
		//{"'(a . (b . (c . (d . (e . ())))))", "(a b c d e)"},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("parser.ParseExpression() error = %v", err)
		}

		var o obj.Object
		o, err = EvalExpression(expr, env)
		if err != nil {
			t.Fatalf("EvalExpression() error = %v", err)
		}

		if o.Inspect() != tt.want {
			t.Errorf("EvalExpression() = %v, want %v", o.Inspect(), tt.want)
		}

	}
}

func TestEvalProgram002(t *testing.T) {
	tests := []struct {
		input string
		want  []string
	}{
		{
			"(define x '(a . b)) (car x)",
			[]string{"", "a"},
		},
		{
			"(define x (cons 'a 'b)) (car x) (cdr x)",
			[]string{"", "a", "b"},
		},
		{
			"(define x (list 'a 'b))  x",
			[]string{"", "(a b)"},
		},
		{
			"(define x (list 'a 'b))  (car x)",
			[]string{"", "a"},
		},
		{
			"(define x (list 'a 'b))  x",
			[]string{"", "(a b)"},
		},
		{
			"(cdr (list 'a 'b))",
			[]string{"(b)"},
		},
		{
			"(define x (list 'a 'b))  (cdr x)",
			[]string{"", "(b)"},
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		exprs, err := parser.ParseProgram()
		if err != nil {
			t.Fatalf("parser.ParseExpression() error = %v", err)
		}

		for i, expr := range exprs {
			var o obj.Object
			o, err = EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if o.Inspect() != tt.want[i] {
				t.Errorf("EvalExpression() = %v, want %v", o.Inspect(), tt.want[i])
			}
		}
	}
}

func TestEvalPairAndList(t *testing.T) {
	tests := []struct {
		input string
		want  []string
	}{
		{
			"(define x '()) x",
			[]string{"", "()"},
		},
		{
			"(cons 'a '())",
			[]string{"(a)"},
		},
		{
			"(cons 'a '(b c))",
			[]string{"(a b c)"},
		},
		{
			"(car '(a b c))",
			[]string{"a"},
		},
		{
			"(cdr '(a b c))",
			[]string{"(b c)"},
		},
		{
			"(car (cons 'a 'b))",
			[]string{"a"},
		},
		{
			"(cdr (cons 'a 'b))",
			[]string{"b"},
		},
		{
			"(list)",
			[]string{"()"},
		},
		{
			"(list 'a)",
			[]string{"(a)"},
		},
		{
			"(list 'a 'b 'c)",
			[]string{"(a b c)"},
		},
		{
			"(define x (cons 'a 'b)) (car x)",
			[]string{"", "a"},
		},
		{
			"(define x (cons 'a 'b)) (cdr x)",
			[]string{"", "b"},
		},
		{
			"(define x (list 'a 'b)) (car x)",
			[]string{"", "a"},
		},
		{
			"(define x (list 'a 'b)) (cdr x)",
			[]string{"", "(b)"},
		},
		{
			"(pair? '())",
			[]string{"#f"},
		},
		{
			"(pair? (cons 'a 'b))",
			[]string{"#t"},
		},
		{
			"(pair? (list 'a 'b))",
			[]string{"#t"},
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)

		// Try to parse as program first
		exprs, err := parser.ParseProgram()
		if err != nil {
			// If that fails, try parsing as single expression
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}
			exprs = []ast.Exp{expr}
		}

		for i, expr := range exprs {
			var o obj.Object
			o, err = EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if o.Inspect() != tt.want[i] {
				t.Errorf("EvalExpression() = %v, want %v", o.Inspect(), tt.want[i])
			}
		}
	}
}
